package org.oschina.helper.query.impl;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.beanutils.ConvertUtils;
import org.oschina.handler.lock.ILockHandler;
import org.oschina.handler.lock.LockHandler;
import org.oschina.helper.cache.ICacheHelper;
import org.oschina.helper.cache.ICachePipeline;
import org.oschina.helper.db.IDBHelper;
import org.oschina.helper.query.BaseQuery;
import org.oschina.helper.query.IQueryCacheCallback;
import org.oschina.helper.query.IQueryHelper;
import org.oschina.helper.query.annotation.Cache;
import org.oschina.util.ReflectUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class QueryHelperImpl extends BaseQuery implements IQueryHelper {

	@Autowired
	private IDBHelper dbHelper;
	@Autowired
	private ICacheHelper cacheHelper;
	@Autowired
	private LockHandler lockHandler;
	
	@Override
	public <T> List<T> query(String sql, Object[] args, Class<T> type)
	{
		return _query(sql,args,type);
	}

	@Override
	public <T> List<T> query(String columns[],String where[],String sort[],Object args[],Class<T> type,int first,int max)
	{
		return query(this._getTableName(type),columns,where,sort,args,type,first,max);
	}
	
	@Override
	public <T>List<T> query(String table,String columns[],String where[],String sort[],Object args[],Class<T> type,int first,int max)
	{
		String sql=this.getSelectSql(table, columns, where, sort, args, type, first, max);
		return query(sql,args,type);
	}

	@Override
	public <T> List<T> query_cache(String sql, Object[] args, Class<T> type)
	{
		return query_cache(this._getCacheName(type),this._getCacheTimeout(type),sql,args,type);
	}

	@Override
	public <T> List<T> query_cache(String columns[],String where[],String sort[],Object args[],Class<T> type,int first,int max)
	{
		String sql=this.getSelectSql(this._getTableName(type), columns, where, sort, args, type, first, max);
		return query_cache(sql,args,type);
	}
	
	@Override
	public <T> List<T> query_cache(String cacheKey, int timeout, String[] columns, String[] where, String[] sort, Object[] args, Class<T> type, int first, int max)
	{
		String sql=this.getSelectSql(this._getTableName(type), columns, where, sort, args, type, first, max);
		return query_cache(cacheKey,timeout,sql,args,type);
	}
	
	private final String QUERY_CACHE_LOCK_KEY="LOCK_QUERY_CACHE_";
	private final int QUERY_CACHE_LOCK_TIMEOUT=10;
	
	@Override
	public <T> List<T> query_cache(String key, int timeout, String sql, Object[] args, Class<T> type)
	{
		String sqlKey=getKey(key,this.sqlParser(sql, args));
		String cacheName=this._getCacheName(type);
		int cacheTimeout=this._getCacheTimeout(type);
		
		if(!cacheHelper.exists(sqlKey))
		{
			QueryCacheLockHandler handler=new QueryCacheLockHandler(sql,args,type,sqlKey,cacheName,cacheTimeout,key,timeout);
			lockHandler.lock(this.getKey(QUERY_CACHE_LOCK_KEY, sqlKey), QUERY_CACHE_LOCK_TIMEOUT, handler);
		}
		
		List<T> result=new ArrayList<T>();
		List<Integer> ids=cacheHelper.lget(sqlKey, 0, -1, Integer.class);
		for(Integer id:ids)
		{
			if(!id.equals(-1))
			{
				Object value=_hget(cacheName,id, type);
				if(value==null)
				{
					System.out.println(value+"|"+id);
					cacheHelper.del(sqlKey);
					return query_cache(key,timeout,sql,args,type);
				}
				result.add((T)value);
			}
		}
		
		return result;
	}
	
	private final String QUERY_CACHE_LIST_LOCK_KEY="LOCK_QUERY_CACHE_LIST_";
	private final int QUERY_CACHE_LIST_LOCK_TIMEOUT=10;
	
	@Override
	public <T> List<T> query_cache(String key, String itemKey, int timeout, int itemTimeout, int maxLength, int first, int max, Class<T> type, IQueryCacheCallback callback)
	{
		if(maxLength!=-1 && this.maxCacheCount(first, max, maxLength))
			return callback.get(first, max);
		
		if(first>=cacheHelper.llen(key) || cacheHelper.lget(key, first, Integer.class).equals(-1))
		{
			ListQueryCacheLockHandler handler=new ListQueryCacheLockHandler(first,max,itemKey,itemTimeout,key,timeout,callback);
			lockHandler.lock(this.getKey(QUERY_CACHE_LIST_LOCK_KEY, key), QUERY_CACHE_LIST_LOCK_TIMEOUT, handler);
		}
		
		List<Integer> ids=cacheHelper.lget(key,first,first+max, Integer.class);
		List<T> result=new ArrayList<T>();
		if(ids!=null && !ids.isEmpty())
		{
			for(Integer id:ids)
			{
				if(!id.equals(-1))
				{
					Object value=_hget(itemKey,id,type);
					if(value==null)
					{
						cacheHelper.del(key);
						return query_cache(key,itemKey,timeout,itemTimeout,maxLength,first,max,type,callback);
					}
					result.add((T)value);
				}
			}
		}
		return result;
	}
	
	@Override
	public <T> List<T> query_cache(String cacheKey, int maxLength, int first, int max, Class<T> type, IQueryCacheCallback callback)
	{
		return query_cache(cacheKey,this._getCacheName(type),this._getCacheTimeout(type),this._getCacheTimeout(type),maxLength,first,max,type,callback);
	}
	
	@Override
	public <T> List<T> query_cache(String key, int timeout, int maxLength, int first, int max, Class<T> type, IQueryCacheCallback callback)
	{
		return query_cache(key,this._getCacheName(type),timeout,this._getCacheTimeout(type),maxLength,first,max,type,callback);
	}
	
	@Override
	public <T> Object queryForObject(String sql, Object[] args, Class<T> type)
	{
		return _queryForObject(sql, args, type);
	}

	@Override
	public <T> Object queryForObject(String table,String columns[],String where[],Object args[],Class<T> type)
	{
		return queryForObject(this.getSelectSql(table, columns, where, null, args, type, -1, -1),args,type);
	}
	
	@Override
	public <T> Object queryForObject(String[] columns, String[] where, Object[] args, Class<T> type)
	{
		return queryForObject(this._getTableName(type),columns,where,args,type);
	}

	@Override
	public <T> Object queryForObject_cache(String[] columns, String[] where, Object[] args, Class<T> type)
	{
		Cache cache=this._getCacheAnnotation(type);
		return queryForObject_cache(cache.name(),cache.timeout(),columns,where,args,type);
	}
	
	@Override
	public <T> Object queryForObject_cache(String key, int timeout, String[] columns, String[] where, Object[] args, Class<T> type)
	{
		String sql=this.getSelectSql(this._getTableName(type), columns, where, null, args, type, -1, -1);
		return queryForObject_cache(key,timeout,sql,args,type);
	}
	
	@Override
	public <T> Object queryForObject_cache(String sql, Object[] args, Class<T> type)
	{
		Cache cache=this._getCacheAnnotation(type);
		return queryForObject_cache(cache.name(),cache.timeout(),sql,args,type);
	}
	
	@Override
	public <T> Object queryForObject_cache(String key, int timeout, String sql, Object[] args, Class<T> type)
	{
		String sqlKey=this.getKey(key, this.sqlParser(sql, args));
		
		if(!cacheHelper.exists(sqlKey))
		{
			Object result=queryForObject(sql,args,type);
			ICachePipeline pipeline=cacheHelper.getPipeline();
			if(ReflectUtil.isBasic(type))
			{
				pipeline.set(sqlKey, result, timeout);
			}
			else
			{
				Object id=this.getId(result);
				pipeline.set(sqlKey, id, timeout);
				_push(pipeline,key,this.getKey(key, id),timeout);
				
				_hset(key,timeout,result);
			}
			_push(pipeline,key,sqlKey,timeout);
			pipeline.sync();
			return result;
		}
		else
		{
			Object result=cacheHelper.get(sqlKey, Object.class);
			if(ReflectUtil.isBasic(type))
				return ConvertUtils.convert(result, type);
			else
				return _hget(key,result, type);
		}
	}
	
	@Override
	public long queryDataSum(String table, String[] where, Object[] args)
	{
		String sql=this.getSumSql(table, where);
		return Long.parseLong(queryForObject(sql, args, Object.class).toString());
	}
	
	@Override
	public long queryDataSum(String[] where, Object[] args, Class type)
	{
		return queryDataSum(this._getTableName(type),where,args);
	}

	@Override
	public long queryDataSum_cache(String key, int timeout, String tableName, String[] where, Object[] args)
	{
		String sql=this.getSumSql(tableName, where);
		return Long.parseLong(queryForObject_cache(key, timeout, sql, args, Object.class).toString());
	}
	
	@Override
	public long queryDataSum_cache(String[] where, Object[] args, Class type)
	{
		Cache cache=this._getCacheAnnotation(type);
		return queryDataSum_cache(cache.name(),cache.timeout(),this._getTableName(type),where,args);
	}
	
	@Override
	public long insert(String[] columns, Object[] args, Class type)
	{
		return insert(this._getTableName(type),columns,args);
	}

	@Override
	public long insert(String table,String columns[],Object args[])
	{
		return _update(this.getInsertSql(table,columns),args);
	}
	
	@Override
	public long insert_cache(String[] columns, Object[] args, Class type)
	{
		return insert_cache(this._getCacheName(type),this._getTableName(type),columns,args);
	}

	@Override
	public long insert_cache(String key, String table, String[] columns, Object[] args)
	{
		long result=insert(table,columns,args);
		if(result!=0)
			_del(null, key);
		return 0;
	}
	
	@Override
	public long update(String[] columns, String[] where, Object[] args, Class type)
	{
		return update(this._getTableName(type),columns,where,args);
	}
	
	@Override
	public long update(String table,String columns[],String where[],Object args[])
	{
		return update(getUpdateSql(table, columns, where),args);
	}
	
	@Override
	public long update(String sql, Object[] args)
	{
		return _update(sql, args);
	}
	
	@Override
	public long update_cache(String[] columns, String[] where, Object[] args, Class type)
	{
		return update_cache(this._getCacheName(type),this._getTableName(type),columns,where,args);
	}

	@Override
	public long update_cache(String key, String table, String[] columns, String[] where, Object[] args)
	{
		return update_cache(key,getUpdateSql(table, columns, where),args);
	}
	
	@Override
	public long update_cache(String key,String sql,Object args[])
	{
		long result=update(sql,args);
		if(result!=0)
			_del(null, key);
		return result;
	}
	
	@Override
	public long delete(String table, String[] where, Object[] args)
	{
		return update(getDeleteSql(table, where), args);
	}

	@Override
	public long delete(String[] where, Object[] args, Class type)
	{
		return delete(this._getTableName(type),where,args);
	}

	@Override
	public long delete_cache(String key, String table, String[] where, Object[] args)
	{
		if(table==null && where==null && args==null)
		{
			_del(null, key);
			return 0;
		}
		
		long result=delete(table,where,args);
		if(result!=0)
			_del(null, key);
		return result;
	}

	@Override
	public long delete_cache(String[] where, Object[] args, Class type)
	{
		return delete_cache(this._getCacheName(type),this._getTableName(type),where,args);
	}
	
	@Override
	public int[] batchUpdate(String table, String[] columns, String[] where, List<Object[]> args)
	{
		return batchUpdate(this.getUpdateSql(table, columns, where),args);
	}
	
	@Override
	public int[] batchUpdate(String sql, List<Object[]> args)
	{
		return _batchUpdate(sql, args);
	}

	@Override
	public int[] batchUpdate(String[] columns, String[] where, List<Object[]> args, Class type)
	{
		return batchUpdate(this._getTableName(type),columns,where,args);
	}

	@Override
	public int[] batchUpdate_cache(String key, String table, String[] columns, String[] where, List<Object[]> args)
	{
		return batchUpdate_cache(key,this.getUpdateSql(table, columns, where),args);
	}

	@Override
	public int[] batchUpdate_cache(String[] columns, String[] where, List<Object[]> args, Class type)
	{
		return batchUpdate_cache(this._getCacheName(type),this.getUpdateSql(this._getTableName(type), columns, where),args);
	}

	@Override
	public int[] batchUpdate_cache(String key, String sql, List<Object[]> args)
	{
		_del(null,key);
		return batchUpdate(sql,args);
	}
	
	private <T> List<T> _query(String sql, Object[] args, Class<T> type)
	{
		return dbHelper.query(sql, args, type);
	}
	
	private long _update(String sql,Object args[])
	{
		return dbHelper.update(sql, args);
	}
	
	private int[] _batchUpdate(String sql,List<Object[]> args)
	{
		return dbHelper.batchUpdate(sql, args);
	}
	
	private <T> Object _queryForObject(String sql,Object args[],Class<T> type)
	{
		return dbHelper.queryForObject(sql, args, type);
	}
	
	/**
	 * 以hash的形式放入缓存  关联的对象会以ID形式储存 _属性名代表需要的属性 并存入新的关联缓存中
	 * @param instance
	 */
	private void _hset(String key,int timeout,Object instance)
	{
		if(instance==null)
			return;
		
		cacheHelper.hset(this.getKey(key, this.getId(instance)), instance, instance.getClass(), timeout);
	}
	
	/**
	 * 和_hset对应的读取方法
	 * @param <T>
	 * @param id
	 * @param type
	 * @param columns
	 * @return
	 */
	private <T>Object _hget(String key,Object id,Class<T> type)
	{
		if(id.equals(-1))
			return null;
		
		return cacheHelper.hget(this.getKey(key,id), type);
	}
	
	private void _push(ICachePipeline pipeline,String key,String item,int timeout)
	{
		if(pipeline==null)
			cacheHelper.lpush(key, item, timeout);
		else
			pipeline.lpush(key, item, timeout);
	}
	
	private void _del(ICachePipeline pipeline,String key)
	{
		boolean newPipeline=false;
		if(pipeline==null)
		{
			newPipeline=true;
			pipeline=cacheHelper.getPipeline();
		}
		
		List<String> items=cacheHelper.lget(key, 0, -1, String.class);
		for(String item:items)
			pipeline.del(item);
		
		pipeline.del(key);
		if(newPipeline)
			pipeline.sync();
	}
	
	class QueryCacheLockHandler implements ILockHandler
	{
		private String sql;
		private Object args[];
		private Class type;
		private String sqlKey;
		private String cacheName;
		private int cacheTimeout;
		private String key;
		private int timeout;
		
		public QueryCacheLockHandler(String sql,Object args[],Class type,String sqlKey,String cacheName,int cacheTimeout,String key,int timeout)
		{
			this.sql=sql;
			this.args=args;
			this.type=type;
			this.sqlKey=sqlKey;
			this.cacheName=cacheName;
			this.cacheTimeout=cacheTimeout;
			this.key=key;
			this.timeout=timeout;
		}
		
		@Override
		public Object handle()
		{
			if(!cacheHelper.exists(sqlKey))
			{
				List resultList=query(sql,args,type);
				ICachePipeline pushPipeline=cacheHelper.getPipeline();
				for(Object result:resultList)
				{
					Object id=getId(result);
					pushPipeline.rpush(sqlKey,id,timeout);
					_push(pushPipeline,key,getKey(key, id),timeout);
					
					_hset(cacheName,cacheTimeout,result);
				}
				
				if(resultList==null || resultList.isEmpty())
					pushPipeline.rpush(sqlKey,-1,timeout);
				
				_push(pushPipeline,key,sqlKey,timeout);
				pushPipeline.sync();
			}
			return null;
		}
	}
	
	class ListQueryCacheLockHandler implements ILockHandler
	{
		private int first;
		private int max;
		private String itemKey;
		private int itemTimeout;
		private String key;
		private int timeout;
		private IQueryCacheCallback callback;
		
		public ListQueryCacheLockHandler(int first,int max,String itemKey,int itemTimeout,String key,int timeout,IQueryCacheCallback callback)
		{
			this.first=first;
			this.max=max;
			this.itemKey=itemKey;
			this.itemTimeout=itemTimeout;
			this.key=key;
			this.timeout=timeout;
			this.callback=callback;
		}
		
		@Override
		public Object handle()
		{
			int cacheLength=(int) cacheHelper.llen(key);
			if(first>=cacheLength || cacheHelper.lget(key, first, Integer.class).equals(-1))
			{
				if(first>=cacheLength)
				{
					ICachePipeline pipeline=cacheHelper.getPipeline();
					for(int j=0,len=first+max-cacheLength;j<len;j++)
						pipeline.rpush(key,-1,timeout);
					pipeline.sync();
				}
				
				ICachePipeline pipeline=cacheHelper.getPipeline();
				List resultList=callback.get(first,max);
				if(resultList!=null && !resultList.isEmpty())
				{
					for(int j=0,len=resultList.size();j<len;j++)
					{
						Object result=resultList.get(j);
						Object id=getId(result);
						pipeline.lset(key, first+j, id, timeout);
						_hset(itemKey,itemTimeout,result);
					}
				}
				pipeline.sync();
			}
			return null;
		}
	}
}